// 类： 类的组成： 构造函数、属性（实例属性，原型属性、静态属性）、方法 （实例的方法，原型方法，静态方法） 访问器， 静态相关的配置
// 实例属性访问器;........................................................................................

class Circle1 {
  // 给这个类来声明属性
  public x;
  public y;
  public fn: () => void;
  constructor(x: number, y: number = 200) {
    // 函数
    this.x = x;
    this.y = y;
    this.fn = () => {};
  }
}

class Circle2 {
  // 给这个类来声明属性
  public x!: number;
  public y!: number;
  public fn!: () => void;
  constructor(x: number, y: number = 200) {
    //   属性“x”没有初始化表达式，且未在构造函数中明确赋值属性“fn”没有初始化表达式，且未在构造函数中明确赋值
    //   public x!: number; 用这种来解决
  }
}

class Circle3 {
  // ?代表可选参数，赋值时要加默认值防止报错
  // = 表示默认值
  private x?: number;
  public y: number;
  public fn: () => void;
  constructor(x: number, y?: number) {
    // 构造函数就是函数，用法同函数
    this.x = x;
    this.y = y || 6666;
    this.fn = () => {};
  }
}

class Circle4 {
  // 给这个类来声明属性
  private x: number;
  public y: number;
  public fn: () => void;
  constructor(x: number, y: number = 200) {
    // 函数
    this.x = x;
    this.y = y;
    this.fn = () => {};
  }
}
// 最终简化版
class Circle5 {
  constructor(public x: number, public y: number = 200) {
    //   不需要赋值
  }
}
// 类的修饰符
// public 公开属性，类的实例在外部可以访问这个属性，类的内部也可以访问，继承的子类也可以访问
// protected (我自己能访问，儿子能访问，外部无法访问)
// private (私有的 自己能访问)
// 平时我们一般采用public 或者private的场景比较多.

// readonly 标识仅读属性，意味着如果初始化后，不能被修改

// let circle = new Circle(100,100)

class Animal1 {
  // # 是js 语法 新增的，表示私有属性
  #xxx = "abc";
  // readonly 标识仅读属性，意味着如果初始化后，不能被修改
  public readonly sex: string = "ppp";
  constructor(private name: string, public age: number) {
    // 父级可以修改
    this.sex = "11";
  }
}
class Cat1 extends Animal1 {
  constructor(name: string, age: number) {
    // super在构造函数 指向的是父类，在原型的方法中调用的时候指向的是父类的原型
    super(name, age);
    // this.name;//子可以访问属性
    // 子级，不可以修改父级/ readonly 标识仅读属性
    // this.sex = "11";
  }
}
const tom = new Cat1("Tom猫", 18);
//使用对象[key]可以访问私有属性 绕过ts检测
//使用对象.key不可以访问私有属性 绕不过ts检测
console.log(tom["name"]);

// 属性访问器;........................................................................................
// 类中的  属性访问器 === object.defineProperty
class Animal2 {
  private _sound: string = "";
  constructor(public name: string, public age: number) {}
  // 会被编译成; object.defineProperty
  get sound() {
    return this.sound;
  }
  // 会被编译成; object.defineProperty
  set sound(value: string) {
    this._sound = value;
  }
}
class Cat2 extends Animal2 {
  constructor(name: string, age: number) {
    super(name, age);
  }
}

let animal = new Cat2("Tom", 18);
animal.sound = "猫猫叫";
console.log(animal.sound);
// 原型方法;........................................................................................

class Animal {
  constructor(protected name: string) {
    // 等价于 每个属性增添了public
  }
  // 原型方法重载
  convert(value: string): string[];
  convert(value: number): number[];
  convert(value: number | string): string[] | number[] {
    if (typeof value === "string") {
      return value.split("");
    } else {
      return value.toString().split("").map(Number);
    }
  }

  // 原型方法 就是每一个实例共享的方法 , 父类提供的方法 子类是可以进行方法重写的
  // 原型的函数：void 意味着是不关心函数的返回值，并不是空的意思
  protected changeName(value: string, age: number): void {
    this.name = value;
  }
  // 原型属性 需要通过访问器来实现
  get aliasName() {
    return "$" + this.name;
  }
  set alisName(name: string) {
    this.name = name;
  }
  // 静态的属性和方法，子类Cat可以继承 Cat.getA,这样调用
  static a = 1;
  static getA() {
    return this.a;
  }
}
class Cat extends Animal {
  constructor(name: string, public readonly age: number) {
    super(name); // Animal.call(this)
    // this.age = age;
  }
  // super在构造函数 指向的是父类，在原型的方法中调用的时候指向的是父类的原型
  // 子类在重写父类方法要兼容父类方法函数类型, 赋予的函数可以兼容父类
  // 参数可以比父类方法函数类型少，但不能多
  protected changeName(value: string) {
    super.changeName(value, 100);
    return "abc";
  }
}
const tom1 = new Cat("tom", 30);
const tom2 = new Cat("tom", 30);

console.log(Cat.getA());

// console.log(tom1.aliasName === tom2.aliasName)
// tom.changeName('jerry')
// 以上用法同es6

// protected constructor().............................................................................................................
// super在 类中访问 constructor / static函数，super指向的都是父类  ,在原型方法， 属性访问器调是指向 super在父类的原型
// 不会new它
class Singleton {
  static instance = new Singleton(); // 自己本身创造一个实例，后续一致用这一个，不产生多个
  protected constructor() {} // 增加protected之后 构造函数不能被new了
  static getInstance() {
    return this.instance;
  }
}
// 类在什么时候 不用外面new
let instance = Singleton.getInstance();

// ts 中有抽象类概念， abstract 不存在的..........................................................................
// 抽象类 可以包含非抽象的方法和属性 ， 不能new它 , 抽象类可以被继承，抽象类中抽象方法子类必须要实现

abstract class Animal6 {
  // 可以描述抽象的属性，但不能描述静态属性 static”修饰符不能与“abstract”修饰符一起使用。
  public abstract a: string;
  // 抽象的方法，，是原型方法，父类没有实现，那么子类必须实现
  abstract eat(): void;
  // 这种写法是实例属性，这个属性要赋值函数
  // abstract play: () => void;
  // 非抽象的方法，已经有实现了，可以直接继承
  drink() {
    console.log("喝水");
  }
}
class Cat6 extends Animal6 {
  // play: () => void; // play就是属性，不赋值时会报错 属性“play”没有初始化表达式，且未在构造函数中明确赋值
  public a!: string;
  eat(): void {
    throw new Error("Method not implemented.");
  }
}
// 因为我们编写的代码的时候 ， 慢慢的脱离继承了。 组合优于继承。   类的装饰器（redux，nest， mobx）

export {};
